package crudfx.map;

import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.input.*;
import javafx.scene.shape.*;
import javafx.scene.image.*;
import crudfx.util.*;
import crudfx.data.*;
import java.lang.*;
import java.awt.image.*;
import javax.swing.*;

/**
 * @profile desktop
 * @author Sergey Surikov
 */
class CachedGoogleMapTile extends CustomNode{
    var view:ImageView=ImageView{};
    /*var rectangle:Rectangle=Rectangle{
        width:256
        height:256
        };*/
    var x:Integer=-1;
    var y:Integer=-1;
    var z:Integer=-1;
    public var updated:Boolean=false;
    function composeURL(x:Integer,y:Integer,z:Integer):String{
        return "http://mt3.google.com/vt/v=w2.97&x={x}&y={y}&z={z+3}";//map
        }
    function composeFileName(x:Integer,y:Integer,z:Integer):String{
        return "{java.lang.System.getProperty("java.io.tmpdir", "")}crudfx.googlemap.{x}.{y}.{z+3}.png";
        }
    public function setImage(){
        var url:String=composeURL(x,y,z);
        var fileName:String=composeFileName(x,y,z);
        var file:java.io.File=new java.io.File(fileName);
        if(not file.exists()){
            crudfx.util.Tools.cacheImage(url,fileName,256,256);
            }
        if(file.exists()){
            /*var imageIcon=new ImageIcon(new java.net.URL(url));
            var bufferedImage:BufferedImage=Tools.toBufferedImage(imageIcon.getImage());
            rectangle.fill=ImagePaint{
                    bufferedImage:bufferedImage
                    };*/
            view.image=Image{ url:file.toURI().toURL().toString() };
            updated=true;
            }
        else{ 
            //view.image=none;
            }
        }
    override public function create():Node{
        return view;
        }
    }
/**
 * @profile desktop
 * @author Sergey Surikov
 */
public class GoogleMap extends CustomNode{
    public var width:Number on replace {fillTiles();};
    public var height:Number on replace {fillTiles();};
    public var zoom:Number on replace {fillTiles();};//0-16 =12
    public var latitude:Number on replace {fillTiles();};//=32.7717399597168
    public var longitude:Number on replace {fillTiles();};//-97.71955108642578
    var refreshing:Boolean=false;
    var base:XY=XY{};
    var shift:XY=XY{};
    var subShift:XY=XY{};
    var unlock:Boolean=false;
    var tx:Number;
    var ty:Number;
    var tiles:CachedGoogleMapTile[];
    var degree:Number[];
    var radian:Number[];
    var ratio:Number[];
    /*var mapDecanter:Decanter=Decanter{
        time:0.1s
        }*/
    var tileDecanter:Decanter=Decanter{
        time:0.1s
        }
    var tileGroup:Group;
    var drag:Group;
    var group:Group=Group{
        //cache:true
        content:[
            Group{
                content:[drag=Group{
                    //content:tiles
                    content:[
                        tileGroup=Group{ content:tiles }                        
                        ]
                    cursor:bind if (refreshing)then Cursor.WAIT else Cursor.MOVE
                    blocksMouse:true;
                    onMousePressed:function(e:MouseEvent):Void{
                        tx=e.sceneX;
                        ty=e.sceneY;}
                    onMouseReleased:function(e:MouseEvent):Void{
                        var xy:XY=mapToPx(longitude,latitude);
                        var lonLan:XY=pxToMap(xy.x-drag.translateX/256,xy.y-drag.translateY/256);
                        unlock=false;
                        longitude=lonLan.x;
                        latitude=lonLan.y;
                        unlock=true;
                        fillTiles();                        
                        }
                    onMouseDragged:function(e:MouseEvent):Void{
                        drag.translateX=e.sceneX-tx;
                        drag.translateY=e.sceneY-ty; }
                    }
                    ,Rectangle{
                            width:bind width
                            height:bind height
                            fill:bind if (refreshing)then Color.web("#999999",0.9) else Color.web("#ffffff",0.02)
                            }
                    ]
                clip:Rectangle{ x:0 y:0 width:bind width  height:bind height }
                }
            ]
        }
    function normalize(){
        while(longitude < -180)longitude=longitude+360.0;
        while(longitude >  180)longitude=longitude-360.0;
        while(latitude  < -85)latitude=latitude+180.0-10;
        while(latitude  >  85)latitude=latitude-180.0+10;
        }
    init{
        var tileSize:Number=8;
        delete degree;
        delete radian;
        delete ratio;
        for(z:Integer in [1 .. 19]){
            insert tileSize/360.0 into degree;
            insert tileSize/(2.0*java.lang.Math.PI) into radian;
            insert tileSize/2.0 into ratio;
            tileSize=tileSize*2;
            }
        }
   postinit{
       unlock=true;
       fillTiles();
       }
    function fillTiles():Void{
        if(not unlock)return;
        refreshing=true;
        //mapDecanter.add(
        startFillTiles();
        //);
        }
    function startFillTiles():Void{
        var w:Number=width/256;
        var h:Number=height/256;
        if(w.intValue()!=w)w=w.intValue()+3;
        if(h.intValue()!=h)h=h.intValue()+3;
        var ww:Integer=w as Integer;
        var hh:Integer=h as Integer;
        shift.x=(ww*256-256-width)/2;
        shift.y=(hh*256-256-height)/2;
        if(ww/2.0==ww/2)shift.x=shift.x+128;
        if(hh/2.0==hh/2)shift.y=shift.y+128;
        normalize();
        base=mapToPx(longitude,latitude);
        subShift.x=256*(base.x-(base.x as Integer));
        subShift.y=256*(base.y-(base.y as Integer));
        var nextTiles:CachedGoogleMapTile[];

        for(y in [1 .. hh]){
            for(x in [1 .. ww]){
                var xx:Integer=x-1+(base.x as Integer)-ww/2;
                var yy:Integer=y-1+(base.y as Integer)-hh/2;
                if(not (xx<2*ratio[zoom as Integer])) xx=(xx-2*ratio[zoom as Integer]) as Integer;
                if(not (yy<2*ratio[zoom as Integer])) yy=(yy-2*ratio[zoom as Integer]) as Integer;
                if(xx<0) xx=(xx+2*ratio[zoom as Integer]) as Integer;
                if(yy<0) yy=(yy+2*ratio[zoom as Integer]) as Integer;
                var cu:CachedGoogleMapTile=findTile(tiles,xx, yy, zoom as Integer);
                if(cu==null)cu=CachedGoogleMapTile{};
                insert cu into nextTiles;
                cu.translateX=256*(x-1);
                cu.translateY=256*(y-1);
                if(not cu.updated){
                    cu.x=xx;
                    cu.y=yy;
                    cu.z=zoom as Integer;
                    }
                }
            }
        var counter=0;
        for(t:CachedGoogleMapTile in nextTiles){
            if(not t.updated){
                counter++;
                Waiter{
                    delay:0.1s
                    action:function():Void{
                        t.setImage();
                        counter--;
                        }
                    onDone:function():Void{
                        
                        }
                    }.serve();
                }
            }
        WaitFor{
            isDone:bind counter==0
            action:function():Void{
                tileDecanter.add(function():Void{
                        this.visible=false;
                        delete tiles;
                        drag.translateX=0;
                        drag.translateY=0;
                        tileGroup.translateY=-shift.y-subShift.y;
                        tileGroup.translateX=-shift.x-subShift.x;
                        insert nextTiles into tiles;
                        delete nextTiles;
                        tileGroup.content=tiles;
                        this.visible=true;
                        refreshing=false;
                        }
                    );
                }
            }.start();
        }
    function findTile(layer:CachedGoogleMapTile[],x:Integer,y:Integer,z:Integer):CachedGoogleMapTile{
        for(t:CachedGoogleMapTile in layer){
            if(t.x==x and t.y==y and t.z==z)return t;
            }
        return null;
        }
    function pxToMap(x:Number,y:Number):XY{
        var longitude:Number=(x-ratio[zoom as Integer])/degree[zoom as Integer];
        var e1:Number=(y-ratio[zoom as Integer])/(-1*radian[zoom as Integer]);
        var latitude:Number=(2*Math.atan(Math.exp(e1))-Math.PI/2)/(Math.PI/180.0);
        return XY{
            x:longitude
            y:latitude
            };
        }
    public function lonLatToXY(lon:Number,lat:Number):XY{
        var p:XY=mapToPx(lon,lat);
        var m:XY=mapToPx(longitude,latitude);
        var re:XY=XY{
            x:256*(p.x-m.x)+width/2.0;
            y:256*(p.y-m.y)+height/2.0;
            };
        return re;
        }
    function mapToPx(longitude:Number,latitude:Number):XY{
        var x:Number=ratio[zoom as Integer]+longitude*degree[zoom as Integer];
        var e=Math.sin(latitude*(Math.PI / 180.0));
        if(e>0.9999){e=0.9999;}
        if(e<-0.9999){e=-0.9999;}
        var y:Number=ratio[zoom as Integer]+0.5*Math.log((1 + e) / (1 - e))*-1*(radian[zoom as Integer]);
        return XY{
            x:x
            y:y
            };
        }
    override public function create():Node{
        return group;
        }
    }


